package com.bruce.log.generate;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.LevelFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.OptionHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 自定义的日志工具类,定义log对象的同时设置输出目录
 * * 需要在logback.xml,logback-spring.xml或自定义的logback-custom.xml中写入基础配置
 * *
 *
 * @author xin.ding
 * @date 2021/5/18 21:37
 */
public class LogbackFactory {
    /** 日志文件大小 */
    private static String maxFileSize = "50MB";
    /** 总大小限制 */
    private static String totalSizeCap = "10GB";
    /** 历史文件个数 */
    private static int maxHistory = 30;
    /** 控制台 */
    private static ConsoleAppender defaultConsoleAppender = null;

    /**
     * init
     */
    static {
        Map<String, Appender<ILoggingEvent>> appenderMap = getAppender();
        appenderMap.forEach((key, appender) -> {
            // 如果logback配置文件中，已存在窗口输出的appender，则直接使用；不存在则重新生成
            if (appender instanceof ConsoleAppender) {
                defaultConsoleAppender = (ConsoleAppender) appender;
            }
        });
    }

    /**
     * 获取自定义的logger日志
     *
     * @param logFile      日志输出文件
     * @param appenderType Append类型
     * @return
     */
    public static Logger getLogger(String logFile, AppenderType appenderType) {
        ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(logFile);
        LoggerContext loggerContext = logger.getLoggerContext();

        Appender appender = null;
        switch (appenderType) {
            case FILE:
                appender = createFileAppender(logFile, loggerContext);
                break;
            case ROLL_FILE:
                appender = createRollFileAppender(logFile, loggerContext);
                break;
            default:
        }

        // 设置不向上级打印信息
        logger.setAdditive(false);
        logger.addAppender(appender);
        if (defaultConsoleAppender != null) {
            logger.addAppender(defaultConsoleAppender);
        }

        return logger;
    }

    /**
     * 创建日志文件的file appender
     *
     * @param name
     * @param loggerContext
     * @return
     */
    private static FileAppender createFileAppender(String name, LoggerContext loggerContext) {
        FileAppender<ILoggingEvent> fileAppender = new FileAppender<>();
        fileAppender.setName(name);
        fileAppender.setFile(name);
        fileAppender.setEncoder(createEncoder(loggerContext));
        fileAppender.setContext(loggerContext);
        fileAppender.start();
        return fileAppender;
    }

    /**
     * 创建日志文件的file appender
     *
     * @param name
     */
    private static RollingFileAppender createRollFileAppender(String name, LoggerContext loggerContext) {
        RollingFileAppender appender = new RollingFileAppender();
        // 这里设置级别过滤器
        //appender.addFilter(createLevelFilter(level));
        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        appender.setContext(loggerContext);
        // appender的name属性
        appender.setName(name.toUpperCase());
        // 读取logback配置文件中的属性值，设置文件名
        String logPath = OptionHelper.substVars(name, loggerContext);
        appender.setFile(logPath);
        appender.setAppend(true);
        appender.setPrudent(false);
        // 加入下面两个节点
        appender.setRollingPolicy(createRollingPolicy(name, loggerContext, appender));
        appender.setEncoder(createEncoder(loggerContext));
        appender.start();
        return appender;
    }

    /**
     * 创建窗口输入的appender
     *
     * @param
     */
    private static ConsoleAppender createConsoleAppender(LoggerContext loggerContext) {
        ConsoleAppender appender = new ConsoleAppender();
        appender.setContext(loggerContext);
        appender.setName("console");
        appender.addFilter(createLevelFilter(Level.DEBUG));
        appender.setEncoder(createEncoder(loggerContext));
        appender.start();
        return appender;
    }

    /**
     * 设置日志的滚动策略
     *
     * @param name
     * @param context
     * @param appender
     */
    private static TimeBasedRollingPolicy createRollingPolicy(String name, LoggerContext context, FileAppender appender) {
        // 读取logback配置文件中的属性值，设置文件名
        String fp = OptionHelper.substVars(name + "_%d{yyyy-MM-dd}_%i.log", context);
        TimeBasedRollingPolicy rollingPolicyBase = new TimeBasedRollingPolicy<>();
        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        rollingPolicyBase.setContext(context);
        // 设置父节点是appender
        rollingPolicyBase.setParent(appender);
        // 设置文件名模式
        rollingPolicyBase.setFileNamePattern(fp);
        SizeAndTimeBasedFNATP sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP();
        // 最大日志文件大小
        sizeAndTimeBasedFNATP.setMaxFileSize(FileSize.valueOf(maxFileSize));
        rollingPolicyBase.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);
        // 设置最大历史记录为30条
        rollingPolicyBase.setMaxHistory(maxHistory);
        // 总大小限制
        rollingPolicyBase.setTotalSizeCap(FileSize.valueOf(totalSizeCap));
        rollingPolicyBase.start();

        return rollingPolicyBase;
    }

    /**
     * description: 设置日志的输出格式
     *
     * @param context
     */
    private static PatternLayoutEncoder createEncoder(LoggerContext context) {
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(context);
        // 格式从logback.xml中获取
        String pattern = OptionHelper.substVars("${log.pattern}", context);
        encoder.setPattern(pattern);
        encoder.setCharset(StandardCharsets.UTF_8);
        encoder.start();
        return encoder;
    }

    /**
     * description: 设置打印日志的级别
     *
     * @param level
     */
    private static Filter createLevelFilter(Level level) {
        LevelFilter levelFilter = new LevelFilter();
        levelFilter.setLevel(level);
        levelFilter.setOnMatch(FilterReply.ACCEPT);
        levelFilter.setOnMismatch(FilterReply.DENY);
        levelFilter.start();
        return levelFilter;
    }

    /**
     * 读取logback配置文件中的所有appender
     */
    private static Map<String, Appender<ILoggingEvent>> getAppender() {
        Map<String, Appender<ILoggingEvent>> appenderMap = new HashMap<>(16);
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
            for (Iterator<Appender<ILoggingEvent>> index = logger.iteratorForAppenders(); index.hasNext(); ) {
                Appender<ILoggingEvent> appender = index.next();
                appenderMap.put(appender.getName(), appender);
            }
        }
        return appenderMap;
    }

    /***
     * Appender 类型
     */
    enum AppenderType {
        /** 控制台输出 */
        CONSOLE,
        /** 输出单个文件 */
        FILE,
        /** 滚动生成文件 */
        ROLL_FILE
    }

}